#include "osa_common.h"
#include "osa_errno.h"
#include "buffer.h"

#include <assert.h>
#include <string.h>


INLINE void _buf_compact(buf_t *buf)
{
    // compact buffer if head is at middle of buffer
    if (buf->head > 0)
    {
        memmove(buf->data, buf->data + buf->head, buf->tail - buf->head);
        buf->tail -= buf->head;
        buf->head = 0;
    }
}

buf_t *buf_init(buf_t *buf, uint8_t *mem, buf_size_t len)
{
    if (buf == NULL || mem == NULL || len == 0)
        return NULL;

    buf->capacity = len;
    buf->data = mem;
    buf->head = buf->tail = 0;
    buf->flags = 0;
    buf->handle = 0;

    return buf;
}

buf_size_t buf_capacity(buf_t *buf)
{
    assert(buf != NULL);


    return buf->capacity;
}

buf_size_t buf_size(buf_t *buf)
{
    assert(buf != NULL);

    return buf->tail - buf->head;
}

buf_size_t buf_left(buf_t *buf)
{
    assert(buf != NULL);

    return buf->capacity - buf->tail;
}

buf_size_t buf_waste(buf_t *buf)
{
    assert(buf != NULL);

    return buf->head;
}

uint8_t *buf_head(buf_t *buf)
{
    assert(buf != NULL);

    return buf->data + buf->head;
}

uint8_t *buf_tail(buf_t *buf)
{
    assert(buf != NULL);

    return buf->data + buf->tail;
}


void buf_clear(buf_t *buf)
{
    assert(buf != NULL);

    buf->head = buf->tail = 0;
    buf->flags = 0;

#ifdef _DEBUG
    memset(buf->data, 0, buf->capacity);
#endif

}

int buf_putc(buf_t *buf, uint8_t value)
{
    assert(buf != NULL);

retry:
    if ((buf->tail + 1) > buf->capacity)
    {
        // compact buffer if possible
        if (buf->head > 0)
        {
            _buf_compact(buf);
            goto retry;
        }
        else
            return -ENOMEM;
    }

    buf->data[buf->tail++] = value;

    return value;
}

int  buf_write(buf_t *buf, uint8_t *value, buf_size_t len)
{
    assert(buf != NULL);

    if (value == NULL && len != 0)
        return -EINVAL;

retry:
    if ((buf->tail + len) > buf->capacity)
    {
        // compact buffer if possible
        if (buf->head > 0)
        {
            _buf_compact(buf);
            goto retry;
        }
        else
        {
            len = buf->capacity - buf->tail;

            if (len == 0)
                return -ENOMEM;
        }
    }

    memcpy(buf->data + buf->tail, value, len);
    buf->tail += len;

    return len;
}

int  buf_getc(buf_t *buf)
{
    int rc = -ENODATA;

    assert(buf != NULL);


    if (buf->head < buf->tail)
    {
        rc = buf->data[buf->head++];
    }

    return rc;
}

int  buf_read(buf_t *buf, uint8_t *value, buf_size_t len)
{
    int rc = 0;

    assert(buf != NULL);

    if (value == NULL && len != 0)
        return -EINVAL;

    if (buf->head < buf->tail)
    {
        rc = MIN(buf->tail - buf->head, len);

        memcpy(value, buf->data + buf->head, rc);

        buf->head += rc;
    }

    return rc;
}

int  buf_ungetc(buf_t *buf, uint8_t value)
{
    assert(buf != NULL);

    if (buf->head > 0)
    {
        buf->data[--buf->head] = value;

        return buf->tail - buf->head;
    }

    return -ENOMEM;
}

int  buf_resize(buf_t *buf, buf_size_t len, int tail)
{
    buf_size_t offset;

    assert(buf != NULL);

retry:
    if (tail)
        offset = buf->tail;
    else
    {
        offset = buf->head;
    }

    len += offset;

    if (len > buf->capacity)
    {
        // compact buffer if possible
        if (buf->head > 0)
        {
            _buf_compact(buf);
            goto retry;
        }
        else
            return -ENOSPC;
    }

    buf->tail = buf->head + len;

    return 0;
}

void buf_remove_tail(buf_t *buf, buf_size_t count)
{
    assert(buf != NULL);

    if ((buf->tail - buf->head) <= count)
    {
        buf->tail = buf->head = 0;
    }
    else
    {
        buf->tail -= count;
    }
}

void buf_remove_head(buf_t *buf, buf_size_t count)
{
    assert(buf != NULL);

    if ((buf->tail - buf->head) <= count)
    {
        buf->tail = buf->head = 0;
    }
    else
    {
        buf->head += count;
    }
}

void buf_compact(buf_t *buf)
{
    assert(buf != NULL);

    _buf_compact(buf);
}

int  buf_reserve(buf_t *buf, buf_size_t len)
{
    assert(buf != NULL);

    if (buf->tail != buf->tail)
        return -EINVAL;

    if ( (buf->head + len) >= buf->capacity)
        return -ENOMEM;

    buf->tail = buf->head += len;

    return buf->head;
}

uint8_t *buf_put (buf_t *buf, buf_size_t len)
{
    uint8_t *tmp;

    assert(buf != NULL);

    if ( (buf->tail + len) > buf->capacity)
        return NULL;

    tmp = buf->data + buf->tail;
    buf->tail += len;

    return tmp;
}

uint8_t *buf_push(buf_t *buf, buf_size_t len)
{
    assert(buf != NULL);

    if (buf->head < len)
    {
        return NULL;
    }

    buf->head -= len;

    return buf->data + buf->head;
}

uint8_t *buf_pull(buf_t *buf, buf_size_t len)
{
    uint8_t *tmp;

    assert(buf != NULL);

    if ( (buf->head + len) > buf->tail)
    {
        return NULL;
    }

    tmp = buf->data + buf->head;

    buf->head += len;

    return tmp;
}

